﻿using HexParse.Datas;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HexParse.Communications
{
    /// <summary>
    /// 串口Bootloader命令解析封包
    /// </summary>
    public class ClassBootloaderForUart
    {
        /********************************************************************
        起始2bytes + 命令类型1byte + 数据长度1byte + 数据6bytes + 结束2bytes
        *********************************************************************/
        /// <summary>
        /// 发送起始标识
        /// </summary>
        private readonly byte[] m_Send_StartFlag = new byte[2] { 0xFA, 0x55 };

        /// <summary>
        /// 发送结束标识
        /// </summary>
        private readonly byte[] m_Send_EndFlag = new byte[2] { 0x56, 0xFB };
        
        /// <summary>
        /// Bootloader帧
        /// FA 55 00 00 00 00 00 00 00 00 56 FB
        /// </summary>
        public readonly byte[] g_EntryBootloaderCmd = new byte[12] { 0xFA, 0x55,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0xFB };

        /// <summary>
        /// 复位帧
        /// FA 55 01 00 00 00 00 00 00 00 56 FB
        /// </summary>
        public readonly byte[] g_ResetCmd = new byte[12] { 0xFA, 0x55, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0xFB };

        
        /// <summary>
        /// 数据结束
        /// FA 55 03 00 00 00 00 00 00 00 56 FB
        /// </summary>
        public readonly byte[] g_DataEndCmd = new byte[12] { 0xFA, 0x55, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0xFB };

        /// <summary>
        /// 检查Bootloader帧
        /// FA 55 03 00 00 00 00 00 00 00 56 FB
        /// </summary>
        public readonly byte[] g_CheckBootloaderCmd = new byte[12] { 0xFA, 0x55, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0xFB };

        /// <summary>
        /// 擦除Flash帧
        /// FA 55 04 00 00 00 00 00 00 00 56 FB
        /// </summary>
        public readonly byte[] g_EraseFlashCmd = new byte[12] { 0xFA, 0x55, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0xFB };

        /// <summary>
        /// 接收起始标识
        /// </summary>
        private readonly byte[] m_Receive_StartFlag = new byte[2] { 0xAF, 0xAA };
        /// <summary>
        /// 接收结束标识
        /// </summary>
        private readonly byte[] m_Receive_EndFlag = new byte[2] { 0xA5, 0xAF };


        /// <summary>
        /// 命令类型
        /// </summary>
        public enum CmdType : byte
        {
            EntryBootloader=0,Reset=1,Data=2,DataEnd,CheckBootloader=4,Earse=5,Other=0xff
        }


        /// <summary>
        /// 一帧数据
        /// </summary>
        public struct OneFrame
        {
            /// <summary>
            /// 本数据所在行
            /// </summary>
            public int lineNum;
            /// <summary>
            /// 帧类型
            /// 命令还是数据
            /// </summary>
            public CmdType cmdType;
            /// <summary>
            /// 包含数据长度
            /// </summary>
            public int dataLength;
            /// <summary>
            /// 一帧数据
            /// 最长4字节
            /// </summary>
            public byte[] data;
        }

        /// <summary>
        /// 打包一行串口数据
        /// data一次不能超过4bytes
        /// </summary>
        public byte[] UartDataPackage(OneFrame oneFrame)
        {
            byte[] dataTmp = new byte[12];

            //开始数据
            dataTmp[0] = m_Send_StartFlag[0];
            dataTmp[1] = m_Send_StartFlag[1];

            //具体内容
            dataTmp[2] = (byte)oneFrame.cmdType;
            dataTmp[3] = Convert.ToByte((oneFrame.lineNum >> 8) & 0xff); //行号高位在前
            dataTmp[4] = Convert.ToByte(oneFrame.lineNum & 0xff); //行号低位在后
            dataTmp[5] = Convert.ToByte(oneFrame.dataLength);

            //数据长度超过0xA0，表示一行最后一组数据
            int realDataLength = oneFrame.dataLength >= 0xA0 ? oneFrame.dataLength - 0xA0 : oneFrame.dataLength;

            //6 7 8 9 4bytes为实际数据
            for (int i = 0; i < realDataLength; i++)
                dataTmp[6 + i] = oneFrame.data[i];

            //结束标记
            dataTmp[10] = m_Send_EndFlag[0];
            dataTmp[11] = m_Send_EndFlag[1];

            return dataTmp;
        }


        /// <summary>
        /// 打包一个指令字串口数据
        /// </summary>
        public byte[] UartDataPackage(DataSource.AddrData oneAddrData)
        {
            byte[] dataTmp = new byte[12];

            //开始数据
            dataTmp[0] = m_Send_StartFlag[0];
            dataTmp[1] = m_Send_StartFlag[1];

            //具体内容
            dataTmp[2] = (byte)CmdType.Data;
            //3-5为地址，高字节在前，3为高位地址,4为低16位的高位地址，5为低16为的低位地址
            dataTmp[3] = Convert.ToByte((oneAddrData.RealAddr >> 16) & 0xff); //页号
            dataTmp[4] = Convert.ToByte((oneAddrData.RealAddr >> 8) & 0xff); //高8位
            dataTmp[5] = Convert.ToByte(oneAddrData.RealAddr & 0xff);


            //6 7 8 9 4bytes为实际数据
            //低字节在前，高字节在后
            for (int i = 0; i < 4; i++)
                dataTmp[6 + i] = Convert.ToByte((oneAddrData.Data >> 8*i) & 0xff);

            //结束标记
            dataTmp[10] = m_Send_EndFlag[0];
            dataTmp[11] = m_Send_EndFlag[1];

            return dataTmp;
        }

        /// <summary>
        /// 判断接收数据
        /// 是否确认对方已经正确接收OK
        /// </summary>
        public CmdType ReceiveDataParse(ref byte[] recData)
        {
            if (recData.Length < 3)
                return CmdType.Other;
            return (CmdType)recData[2];
        }

        /// <summary>
        /// 数组比较
        /// </summary>
        private bool ArrayCompare<T>(T[] data1, T[] data2)
        {
            if (data1.Length!=data2.Length)
                return false;

            for (int i = 0; i < data1.Length; i++)
            {
                if (data1[i].ToString() != data2[i].ToString())
                    return false;
            }

            return true;

        }




        #region 用于测试MCU端接收烧录

        public DataSource.AddrDataListClass test_AllAddrData = new DataSource.AddrDataListClass();
        public ClassBootloaderForUart()
        {
            test_AllAddrData.AddrDataCollection = new System.Collections.ObjectModel.ObservableCollection<DataSource.AddrData>();
        }

        void InnerFlash_WriteTwoWordsFlash(ushort tablePage,ushort offset,ushort[] data,ushort length)
        {
            ushort[] dataTmp = new ushort[4];
            
            for (int i = 0; i < length; i += 4)
            {
                //数组偶数位为指令字高16位(高8位为虚拟字节，仅低8位有效)，奇数位为指令字低16位
                //每个指令字存入一个实际16位数据，高8位为0
                dataTmp[0] = data[i];
                dataTmp[1] = data[i + 1];
                dataTmp[2] = data[i + 2];
                dataTmp[3] = data[i + 3];
                //每次写入2个指令字，偏移4个地址，每2个地址表示一个指令字
                DataSource.AddrData oneData1 = new DataSource.AddrData();
                oneData1.No = test_AllAddrData.AddrDataCollection.Count;
                oneData1.RealAddr = (((uint)tablePage) << 16) + offset+(uint)i;
                oneData1.Data = (((uint)dataTmp[i + 1]) << 16) + dataTmp[i];
                test_AllAddrData.AddrDataCollection.Add(oneData1);
                DataSource.AddrData oneData2 = new DataSource.AddrData();
                oneData2.No = test_AllAddrData.AddrDataCollection.Count;
                oneData2.RealAddr = (((uint)tablePage) << 16) + offset+(uint)(i+2);
                oneData2.Data = (((uint)dataTmp[i + 3]) << 16) + dataTmp[i+2];
                test_AllAddrData.AddrDataCollection.Add(oneData2);
            }
        }

        ushort m_LastTablePage = 0, m_LastOffset = 2; //要写入的首地址，默认为2，即第一次判断认为上一次是第二个，非第一个，不用填充上一个，防止第一次误判
        ushort[] m_DataTmp = new ushort[4]; //存储要写入的2个指令字，每个指令字2个16位组成

        //解析Hex文件
        //0、1表示开头
        //2表示类型
        //3、4、5表示地址，高字节在前，最高字节无
        //6、7、8、9表示数据，低字节在前
        public void UserFlash_DataParseAddrData(byte[] data, byte length)
        {
            ushort addrHighLow = 0, addrLowHigh = 0, addrLowLow = 0;
            ushort dataHighHigh = 0, dataHighLow = 0, dataLowHigh = 0, dataLowLow = 0;
            ushort tablePage = 0, offset = 0;


            addrHighLow = data[3];
            addrLowHigh = data[4];
            addrLowLow = data[5];

            dataLowLow = data[6];
            dataLowHigh = data[7];
            dataHighLow = data[8];
            dataHighHigh = data[9];


            //页数
            tablePage = addrHighLow;

            //页内地址
            offset = Convert.ToUInt16((addrLowHigh << 8) + addrLowLow);


            if (offset % 4 == 0) //本次地址是第一个
            {
                if (m_LastOffset % 4 == 0) //上一次也是第一个，则先填充上一次第二个指令字，写入flash，然后本次重新开始
                {
                    m_DataTmp[2] = 0xffff; //第二个指令字低16位
                    m_DataTmp[3] = 0x00ff; //第二个指令字高16位，最高8位为虚字节，默认为0
                    InnerFlash_WriteTwoWordsFlash(m_LastTablePage, m_LastOffset, m_DataTmp, 4); //上一次数据组合后，写入2指令字
                }
                //上一次是第1个或者第2个，本次都要重新开始
                m_DataTmp[0] = Convert.ToUInt16(dataLowLow + (dataLowHigh << 8)); //第一个指令字低16位
                m_DataTmp[1] = Convert.ToUInt16(dataHighLow + (dataHighHigh << 8)); //第一个指令字高16位，最高8位为虚字节，默认为0

            }
            else //本次地址是第二个
            {
                if (m_LastOffset % 4 == 0) //上一次是第一个，要继续判断是否连续
                {
                    if ((tablePage == m_LastTablePage) && ((offset - m_LastOffset) == 2)) //与上一次连续，填充到上一次的数据中，并写入2个指令字
                    {
                        m_DataTmp[2] = Convert.ToUInt16(dataLowLow + (dataLowHigh << 8)); //第二个指令字低16位
                        m_DataTmp[3] = Convert.ToUInt16(dataHighLow + (dataHighHigh << 8)); //第二个指令字高16位，最高8位为虚字节，默认为0
                        /************* 此处有改动，地址应该写到上一个 *************/
                        InnerFlash_WriteTwoWordsFlash(m_LastTablePage, m_LastOffset, m_DataTmp, 4); //本次次数据组合后，写入2指令字
                    }
                    else //与上一次不连续，填充上一次的第二个指令字，然后填充本次的第一个指令字，同时两次共写入4个指令字
                    {
                        m_DataTmp[2] = 0xffff; //第二个指令字低16位
                        m_DataTmp[3] = 0x00ff; //第二个指令字高16位，最高8位为虚字节，默认为0
                        InnerFlash_WriteTwoWordsFlash(m_LastTablePage, m_LastOffset, m_DataTmp, 4); //上一次数据组合后，写入2指令字

                        m_DataTmp[0] = 0xffff; //第二个指令字低16位
                        m_DataTmp[1] = 0x00ff; //第二个指令字高16位，最高8位为虚字节，默认为0
                        m_DataTmp[2] = Convert.ToUInt16(dataLowLow + (dataLowHigh << 8)); //第二个指令字低16位
                        m_DataTmp[3] = Convert.ToUInt16(dataHighLow + (dataHighHigh << 8)); //第二个指令字高16位，最高8位为虚字节，默认为0
                        /************* 此处有改动，地址应该写到第一个 *************/
                        InnerFlash_WriteTwoWordsFlash(tablePage, (ushort)(offset -2), m_DataTmp, 4); //本次次数据组合后，写入2指令字
                    }
                }
                else//上一次也是第2个，将本次第一个指令字也填充，写入
                {
                    m_DataTmp[0] = 0xffff; //第二个指令字低16位
                    m_DataTmp[1] = 0x00ff; //第二个指令字高16位，最高8位为虚字节，默认为0
                    m_DataTmp[2] = Convert.ToUInt16(dataLowLow + (dataLowHigh << 8)); //第二个指令字低16位
                    m_DataTmp[3] = Convert.ToUInt16(dataHighLow + (dataHighHigh << 8)); //第二个指令字高16位，最高8位为虚字节，默认为0
                    /************* 此处有改动，地址应该写到第一个 *************/
                    InnerFlash_WriteTwoWordsFlash(tablePage, (ushort)(offset - 2), m_DataTmp, 4); //本次次数据组合后，写入2指令字
                }
            }

            //赋予本次数据
            m_LastTablePage = tablePage;
            m_LastOffset = offset;


            //为防止出现奇指令，所以


            //写入Flash，每4个一组，占2个指令字，第一个指令字-低字节、高字节，第二个指令字-低字节、高字节
            //Length必须为4的倍数数，双指令字写入模式
            //例如 data[4]={0xaa05,0x0011,0xaa06,0x0022};，存入0x004000，即0x004000存入0x11aa05,0x004002存入0x22aa06
            //InnerFlash_WriteTwoWordsFlash(addrHighLow,,m_DataTmp,8);

        }
        #endregion
    }
}
